Ну вот и подходит к концу второй модуль, осталось самое важное задание! Любый уважающий себя исследователь должен уметь нарисовать трехмерный чайник в R!

Условие задачи

Датасет (Можно найти по ссылке) содержит информацию о полигонах трехмерной модели чайника. Три столбца таблицы задают координаты (x, y, z), а тройки строк задают треугольники (т. е. строки 1, 2, 3 - первый треугольник, 4, 5, 6 - второй, и так далее).

Напишите функцию, которая будет принимать data.table с этими данными, и возвращать объект plotly с трехмерной моделью. Следует воспользоваться установкой индексов i, j, k.

В данной статье я постараюсь пошагово объяснить и нагялдно показать, как “нарисовать” чайник при помощи языка R.

Решение

Итак, для начала подключим необходимые библиотеки, это “plotly” и “data.table”

library ('plotly')
library ('data.table')

Затем загрузим наш датасет. Важно, чтобы он находился в рабочей директории, иначе, при загрузке, путь к нему нужно будет прописывать вручную.

teapot <- read.csv ('teapot.csv', sep = ";")

Можно посмотреть стуруктуру датасета, однако, там и без того всё понятно, тем более, что переменные уже находятся в нужном виде. Однако, если по какой-либо причине необходимо, к примеру, поменять тип переменных, то тогда, сначала смотрим стурктуру, затем работаем с переменными.

Зададим координаты полигонов, из которых, впоследствии, будет собрана фигура.

mesh <- data.table (x = rnorm (40),
                    y = rnorm (40),
                    z = rnorm (40))

Координаты указаны, теперь необходимо задать векторы, согласно которым данные полигоны будут “уложены”.

i = teapot [1 : 3, 1]
j = teapot [1 : 3, 2]
k = teapot [1 : 3, 3]

Построим трёхмерную координатную сетку, в которой будет отображена фигура

plot_ly (teapot [1 : 3, ], type = "mesh3d",
         x = ~x, y = ~y, z = ~z,
         i = ~i, j = ~j, k = ~k)

Важно помнить, что для корректной работы кода при указании координат необходимо использовать тильду, однако, при проверке кода с помощью он-лайн средств, таких как “stdin → stdout”, тильды необходимо убирать.

Теперь следует указать сами полигоны, которые составят фигуру

i.s <- seq (from = 0, to = nrow (teapot) - 1, by = 3)
j.s <- seq (from = 1, to = nrow (teapot), by = 3)
k.s <- seq (from = 2, to = nrow (teapot), by = 3)

Функция “seq” генерирует последовательность чисел от 0 для вектора i (соответственно, 1 для j и 2 для k) до количества строк в нашем датасете с шагом в 3. Иными словами, каждые три строки датасета определяют один полигон.

Построим фигуру, используя функцию “plot_ly” библиотеки “plotly”.

plot_ly (teapot,
         x = ~x, y = ~y, z = ~z,
         i = ~i.s, j = ~j.s, k = ~k.s,
         type = "mesh3d")

На выходе получаем тот самый чайник, который обязан уметь рисовать любой исследователь.

Осталось только “запаковать” всё это в функцию, согласно условияю задачи и выводить результат по команде.

make.fancy.teapot <- function (teapot.coords) {
  i.s <- seq (from = 0, to = nrow (teapot.coords) - 1, by = 3)
  j.s <- seq (from = 1, to = nrow (teapot.coords), by = 3)
  k.s <- seq (from = 2, to = nrow (teapot.coords), by = 3)
  plot_ly (teapot.coords,
           x = ~x, y = ~y, z = ~z,
           i = ~i.s, j = ~j.s, k = ~k.s,
           type = "mesh3d")
}
make.fancy.teapot (teapot)

По хорошему, согласно условию задачи, следовало бы датасет конвертировать в дататейбл, однако, не вижу в этом необходимости, поскольку код и без него прекрасно работает.

Альтернативные методы решения

Почти любая задача имеет больше одного пути решения, особенно если это касается языков программирования, кода и всех таких делов. Данный случай не является исключением и ниже я привожу ещё два варианта решения. Не вижу смысла столь подробно описывать свои действия, ведь если разобраться, то решение аналогично, просто выполнено при помощи несколько иных функций.

Итак…

Альтернативное решение №1

make.fancy.teapot <- function (teapot.coords) {
  is <- (1 : (nrow (teapot.coords) / 3)) * 3 - 3
  js <- (1 : (nrow (teapot.coords) / 3)) * 3 - 2
  ks <- (1 : (nrow (teapot.coords) / 3)) * 3 - 1
  plot_ly (teapot.coords,
           x = ~x,
           y = ~y,
           z = ~z,
           i = ~is, 
           j = ~js,
           k = ~ks, type = 'mesh3d')
}
make.fancy.teapot (teapot)

Данное решение было предложено обучающей платформой Stepik (оно появляется при условии правильного выполнения задания), на которой, собственно, и была размещена данная задача, как проверка, насколько хорошо студенты усвоили материал курса. Оригинальный текст решения выглядит так:

make.fancy.teapot <- function(teapot.coords) {
  is <- (1 : (nrow (teapot.coords) / 3)) * 3 - 3
  js <- (1 : (nrow (teapot.coords) / 3)) * 3 - 2
  ks <- (1: (nrow (teapot.coords) / 3)) * 3 - 1
  
plot_ly (teapot.coords, x = x, y = y, z = z,
               i = is, 
               j = js,
               k = ks, type='mesh3d')
}

Но при исполнении этого кода в RStudio приведёт к ошибке:

## Error in plot_ly(teapot.coords, x = x, y = y, z = z, i = is, j = js, k = ks, : объект 'x' не найден

Всё дело в том, что сайт и компилятор (в частности, RStudio) используют разные версии языка, и отсутствие тильды в коде координат векторов и полигонов имеет критическое значение. По правде сказать, я сам не один час потратил на выявление проблемы, пока, наконец, не залез в комментарии, где добрые люди, столкнувшиеся с данной проблемой, не указали на необходимость удалить тильды при вставке кода в поле ответа на сайте.

Альтернативное решение №2

make.fancy.teapot <- function (teapot.coords) {
  ind <- data.table (i = seq (0, nrow (teapot.coords) -1, by = 3),
                     j = seq (1, nrow (teapot.coords) -1, by = 3),
                     k = seq (2, nrow (teapot.coords) -1, by = 3))
  plot_ly (teapot.coords,
           x = teapot.coords$x, 
           y = teapot.coords$y, 
           z = teapot.coords$z, 
           i = ind$i, 
           j = ind$j, 
           k = ind$k, 
           type = "mesh3d")
}

Это моё решение, однако, я не стал указывать его в качестве основного из-за того, что там более наглядно показаны все преобразования и все действия. По сути, эти два решения аналогичны, но здесь я указал координаты векторов и полигонов явно.

Заключение

Лично я не могу достоверно сказать, какое прикладное значение имеет данная задача, но она очень хорошо демонстрирует возможности динамической визуализации объектов при помощи языка R. Возможно, я неверно интерпретировал некоторые действия, неверно назвал те или иные объекты, однако, я не специалист в данной области и изучаю это всего месяц. Но уже сейчас могу сказать, что без знания данного языка и без умения использовать данные инструменты далеко в исследованиях не продвинешься.

©Arik Taranis